package client;

import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.Date;
import java.io.UnsupportedEncodingException;

public class MyClient{
	public static void main(String[] args){
		Connection conn;
		try{
			String cmd, message;
			Scanner sc = new Scanner(System.in);
			conn = new Connection(9909, "127.0.0.1");
			System.out.println("Intialized...");
			conn.start();
			System.out.println(String.format("Connect to server at %s", new Date()));
			do{
				System.out.print("\nCommand: ");
				cmd = sc.nextLine();
				
				if(cmd.equals("stop")){
					conn.disconnect();
					conn.join();
					System.out.println(String.format("Disconnected at %s", new Date()));
				}else if(cmd.startsWith("send")){
					message = cmd.replace("send", "");
					conn.sendData(message.getBytes("utf-8"));
				}else if(cmd.equals("help")){
					System.out.println("Command:");
					System.out.println("		send <msg>: Send msg to all client\n");
					System.out.println("		stop: stop and exit the server");
				}else if(cmd.equals("gendata")){
					
				}
			}while(!cmd.equals("stop"));
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
}
	
class Connection extends Thread{
	
		private static final int QUEUE_CAPACITY = 1024;
		private static final byte BEG1 = (byte)0x41;
		private static final byte BEG2 = (byte)0x55;
		private static final byte END1 = (byte)0xFE;
		private static final byte END2 = (byte)0xFF;
		
		private Socket socket;
		private ByteQueue queue;
		private Thread queueHandlingThread;
		private volatile boolean isConnected;
		private String serverHost = "localhost";
		private int port;
		private OutputStream out;
		
		public Connection(int port, String server){
			this.port = port;
			serverHost = server;
			initService();
			try{
				out = socket.getOutputStream();
			}catch(IOException e){e.printStackTrace();}
		}
		
		private void initService(){
			try{
				socket = new Socket(serverHost, port);
				queue = new ByteQueue(QUEUE_CAPACITY);
				isConnected = true;
				System.out.println("Connected to server");
				queueHandlingThread = new Thread(new Runnable(){
					public void run(){
						handlingQueue();
					}
				});
				queueHandlingThread.start();
				
			}catch(IOException ex){
				ex.printStackTrace();
			}
		}
		
		public boolean isConnected(){
			return isConnected;
		}
		
		public void disconnect(){
			isConnected = false;
			try{
				socket.close();
				System.out.println(String.format("Disconnected from server"));
			}catch(IOException e3){e3.printStackTrace();}
		}
		
		@Override
		public void run(){
			try{
				InputStream inStream = socket.getInputStream();
				byte[] buff = new byte[512];
				int i, byteRead = 0;
				while(isConnected){
					if( (byteRead = inStream.read(buff, 0, 512)) >0 ){
						//System.out.println(String.format("Receive: %s (print directly)", new String(buff, "utf-8")));
						queue.add(buff, byteRead);
						
						for(i=0;byteRead>=3 && i<byteRead-2;i++)	{
							if(buff[i]==END1 && buff[i+1]==END1 && buff[i+2]==END2)
								isConnected = false;
						}
					}
					//System.out.println(String.format("Receive: %d (bytes)", byteRead));
				}
			}catch(IOException e1){
				e1.printStackTrace();
			}catch(InterruptedException e2){
				e2.printStackTrace();
			}
		}
		
		private void handlingQueue(){
			try{
				byte[] data;
				while(true){
					synchronized(queue){
						queue.wait();
						data = queue.removeAll();
							if(data != null && data.length > 0){
								System.out.println(String.format("Receive: %s", new String(data, "utf-8")));
							}
						
						queue.notifyAll();
					}
				}
			}catch(InterruptedException e1){
				e1.printStackTrace();
			}catch(UnsupportedEncodingException e2){
				e2.printStackTrace();
			}
		}
		
		public void sendData(byte[] data){
			try{
				out.write(data);
				out.flush();
			}catch(IOException ex){
				ex.printStackTrace();
			}
		}
}

class ByteQueue{
	private byte[] queue;
	private int size;
	private int capacity;
	private int head, tail;
	
	public ByteQueue(int cap){
		capacity = (cap < 1)? 1: cap;
		queue = new byte[capacity];
		size = 0;
		head = tail = 0;
	}
	
	public int getCapacity(){
		return capacity;
	}
	
	public synchronized int getSize(){
		return size;
	}
	
	public synchronized boolean isEmpty(){
		return (size<=0)?true:false;
	}
	
	public synchronized boolean isFull(){
		return size>=capacity?true:false;
	}
	
	public synchronized void waitWhileFull() throws InterruptedException {
		while(isFull()){
			wait();
		}
	}
	
	public synchronized void waitWhileEmpty() throws InterruptedException{
		while(isEmpty()){
			wait();
		}
	}
	
	public synchronized void add(byte b) throws InterruptedException{
		waitWhileFull();
		
		queue[head] = b;
		head = (head + 1) % capacity;
		size++;
		
		//let any waiting threads know about change
		notifyAll();
	}
	
	public synchronized void add(byte[] lst) throws InterruptedException{
		int ptr = 0;
		while(ptr < lst.length){
			waitWhileFull();
			int space = capacity - size;
			int d2End = capacity - head;
			int blockLen = Math.min(space, d2End);
			int byteRemaining = lst.length - ptr;
			int cpyLen = Math.min(blockLen, byteRemaining);
			
			System.arraycopy(lst, ptr, queue, head, cpyLen);
			head = (head + cpyLen)%capacity;
			size += cpyLen;
			ptr += cpyLen;
			notifyAll();
		}
	}
	
	public synchronized void add(byte[] lst, int len) throws InterruptedException{
	
		if(len > lst.length)
			len = lst.length;
			
		int ptr = 0;
		while(ptr < len){
			waitWhileFull();
			int space = capacity - size;
			int d2End = capacity - head;
			int blockLen = Math.min(space, d2End);
			int byteRemaining = len - ptr;
			int cpyLen = Math.min(blockLen, byteRemaining);
			
			System.arraycopy(lst, ptr, queue, head, cpyLen);
			head = (head + cpyLen)%capacity;
			size += cpyLen;
			ptr += cpyLen;
			notifyAll();
		}
	}
	
	public synchronized byte remove() throws InterruptedException{
		waitWhileEmpty();
		
		byte b = queue[tail];
		tail = (tail + 1) % capacity;
		size--;
		
		notifyAll();
		return b;
	}
	
	public synchronized byte[] removeAll()throws InterruptedException{
		if(isEmpty()){
			return new byte[0];
		}
		
		//base on the current size
		byte[] lst = new byte[size];
		
		//copy in the block from tail to end
		int dist2End = capacity - tail;
		int cpyLen = Math.min(size, dist2End);
		System.arraycopy(queue, tail, lst, 0, cpyLen);
		
		//if data wraps around
		if(size > cpyLen)
			System.arraycopy(queue, 0, lst, cpyLen, size - cpyLen);
		
		tail = (tail + size) % capacity;
		size = 0;
		
		notifyAll();
		
		return lst;
	}
}